package com.java.simple.smallspring.beans.factory.support;

import com.java.simple.smallspring.beans.BeansException;
import com.java.simple.smallspring.beans.factory.DisposableBean;
import com.java.simple.smallspring.beans.factory.ObjectFactory;
import com.java.simple.smallspring.beans.factory.config.SingletonBeanRegistry;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zhoujunlin
 * @date 2022年08月17日 17:37
 * @desc
 */
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    /**
     * 内部使用的空对象 因为ConcurrentHashMap不能放null值
     */
    protected static final Object NULL_OBJECT = new Object();
    /**
     * 一级缓存 普通对象
     */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    /**
     * 二级缓存 提前暴露对象  没有完全实例化的对象
     */
    protected final Map<String, Object> earlySingletonObjects = new HashMap<>();

    /**
     * 三级缓存  存放代理对象
     */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

    private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>();

    @Override
    public Object getSingleton(String beanName) {
        Object singletonObject = singletonObjects.get(beanName);
        // 一级缓存中获取不到  再从二级缓存中获取
        if (Objects.isNull(singletonObject)) {
            singletonObject = earlySingletonObjects.get(beanName);
            // 判断二级缓存中是否有对象  这个对象就是代理对象  因为只有代理对象才会放到三级缓存
            if (Objects.isNull(singletonObject)) {
                ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
                if (Objects.nonNull(singletonFactory)) {
                    singletonObject = singletonFactory.getObject();
                    // 把三级缓存中的代理对象中的真实对象获取出来  放入二级缓存
                    earlySingletonObjects.put(beanName, singletonObject);
                    singletonFactories.remove(beanName);
                }
            }
        }
        return singletonObject;
    }

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        singletonObjects.put(beanName, singletonObject);
        earlySingletonObjects.remove(beanName);
        singletonFactories.remove(beanName);
    }

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
        }
    }

    public void registerDisposableBean(String beanName, DisposableBean disposableBean) {
        disposableBeans.put(beanName, disposableBean);
    }

    public void destroySingletons() {
        Set<String> beanNames = this.disposableBeans.keySet();
        Object[] disposableBeanNames = beanNames.toArray();

        for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
            String beanName = (String) disposableBeanNames[i];
            DisposableBean disposableBean = disposableBeans.remove(beanName);
            try {
                disposableBean.destroy();
            } catch (Exception e) {
                throw new BeansException("Destroy method on bean with name '" + beanName + "' threw an exception", e);
            }
        }
    }

}
